Analisi delle Correlazioni - Questionario sui Giovani e la Città di Bari¶

Obiettivo dell'Analisi¶

Questo notebook analizza i dati raccolti attraverso un questionario rivolto ai residenti a Bari. L'obiettivo è identificare le correlazioni tra diverse variabili per comprendere meglio.

Cosa sono le Correlazioni?¶

Una correlazione misura quanto due variabili sono collegate tra loro:

  • +1: correlazione perfetta positiva (quando una aumenta, anche l'altra aumenta)
  • 0: nessuna correlazione (le variabili sono indipendenti)
  • -1: correlazione perfetta negativa (quando una aumenta, l'altra diminuisce)

In questo studio consideriamo significative le correlazioni ≥ 0.4 (moderate-forti).

In [1]:
# Importazione delle librerie necessarie per l'analisi
import pandas as pd  # Per manipolare i dati in formato tabellare
import warnings
warnings.simplefilter(action='ignore', category=pd.errors.PerformanceWarning)  # Nasconde avvisi non critici
from sklearn.preprocessing import MultiLabelBinarizer  # Per convertire risposte multiple in variabili binarie
import re  # Per manipolare testo con espressioni regolari
import json  # Per salvare risultati in formato JSON

1. Preparazione dei Dati sugli Spazi¶

Prima parte dell'analisi: processare i dati relativi agli spazi frequentati dai rispondenti.

Il file "spazi mapped.csv" contiene informazioni sui luoghi che i giovani frequentano a Bari, mappati in categorie specifiche.

In [2]:
# Caricamento del file con i dati sugli spazi mappati
spazi = pd.read_csv("spazi mapped.csv")
In [3]:
# TRASFORMAZIONE DEI DATI DEGLI SPAZI
# Il file contiene una colonna 'Map' con valori separati da virgole (es: "Bar, Università, Centro commerciale")
# Dobbiamo trasformare questi dati in colonne separate per ogni tipo di spazio

# Prima, raccogliamo tutti i tipi di spazi unici presenti nei dati
all_values = set()  # Un set per evitare duplicati
for values in spazi['Map'].dropna():  # Per ogni riga non vuota nella colonna Map
    # Dividiamo i valori per virgola e aggiungiamo al set
    all_values.update([v.strip() for v in values.split(',')])

# Ora creiamo una colonna binaria (0/1) per ogni tipo di spazio
for value in all_values:
    # Per ogni tipo di spazio, creiamo una colonna "Luogo [tipo]"
    # che vale 1 se il rispondente frequenta quel tipo di spazio, 0 altrimenti
    spazi[f"Luogo {value}"] = spazi['Map'].apply(
        lambda x: 1 if pd.notna(x) and value in [v.strip() for v in x.split(',')] else 0
    )
In [4]:
# Rimuoviamo la colonna originale 'Map' perché ora abbiamo le colonne separate
spazi.drop('Map', axis=1, inplace=True)

2. Preparazione dei Dati del Questionario¶

Seconda parte: processare le risposte del questionario.

Il questionario contiene domande di diverso tipo:

  • Domande a scelta singola (es: età, genere)
  • Domande a scelta multipla (es: eventi a cui si partecipa)
  • Scale di valutazione (es: livello di soddisfazione)

Tutte queste risposte devono essere convertite in variabili numeriche per poter calcolare le correlazioni.

In [5]:
# Caricamento del file Excel con le risposte al questionario
questionario = pd.read_excel("questionario anonimo.xlsx")
In [6]:
# Rimozione di colonne non necessarie per l'analisi
questionario.drop(["@dropdown"], axis=1, inplace=True)
In [7]:
# DEFINIZIONE DEI TIPI DI DOMANDE
# Identifichiamo quali colonne (domande) richiedono quale tipo di elaborazione

# Domande a scelta singola - verranno convertite con "one-hot encoding"
# (ogni possibile risposta diventa una colonna separata con valore 0 o 1)
toHotEncode = [0, 1, 2, 3, 4, 6, 7, 11, 12, 13, 14, 19, 20, 22, 23]

# Domande a scelta multipla - prima divise poi convertite
toSplitAndHotEncode = [5, 15, 18]
In [8]:
# Otteniamo i nomi delle colonne da processare
columns_to_encode = [questionario.columns[i] for i in toHotEncode]
In [9]:
# ONE-HOT ENCODING PER DOMANDE A SCELTA SINGOLA
# Esempio: se la domanda "Genere" ha risposte ["Maschio", "Femmina", "Altro"]
# diventa tre colonne: "Genere_Maschio", "Genere_Femmina", "Genere_Altro"
# con valori 1 per la risposta data e 0 per le altre

encoded_df = pd.get_dummies(questionario[columns_to_encode + ["Quali?"]], 
                           columns=columns_to_encode,
                           prefix_sep=' ')  # Separatore tra nome domanda e risposta
In [10]:
# Identifichiamo le colonne rimanenti (quelle non ancora processate)
remaining_columns = [col for i, col in enumerate(questionario.columns) if i not in toHotEncode]
In [11]:
# FUNZIONE PER PROCESSARE DOMANDE A SCELTA MULTIPLA
# Alcune domande permettono di selezionare più risposte
# (es: "Quali eventi frequenti?" -> "Concerti, Teatro, Mostre")

def onehot_encode_multiselect_columns(df, columns_to_encode):
    """
    Converte colonne con risposte multiple in colonne binarie separate.
    
    Esempio di trasformazione:
    "Concerti, Teatro" -> [Concerti: 1, Teatro: 1, Mostre: 0, ...]
    """
    # Iniziamo con la colonna identificativa "Quali?"
    df_encoded = df[["Quali?"]].copy()
    
    for col in columns_to_encode:
        # Otteniamo il nome della colonna
        if isinstance(col, int):
            col_name = df.columns[col]
        else:
            col_name = col
                    
        # Dividiamo le risposte multiple usando le virgole
        responses = df[col_name].fillna('').astype(str)
        
        # Funzione per dividere correttamente il testo
        # Dividiamo su ", " seguito da lettera maiuscola
        def split_on_comma_capital(text):
            parts = re.split(r', (?=[A-Z])', text)
            return parts
        
        split_responses = responses.apply(split_on_comma_capital)
        
        # Puliamo i dati rimuovendo spazi extra e valori vuoti
        cleaned_responses = []
        for response_list in split_responses:
            cleaned = [item.strip() for item in response_list if item.strip()]
            cleaned_responses.append(cleaned if cleaned else [''])
        
        # Raccogliamo tutte le possibili risposte
        all_choices = set()
        for response_list in cleaned_responses:
            all_choices.update(response_list)
        all_choices.discard('')  # Rimuoviamo la stringa vuota
                
        # Creiamo colonne binarie per ogni possibile risposta
        mlb = MultiLabelBinarizer()
        encoded_matrix = mlb.fit_transform(cleaned_responses)
        
        # Creiamo i nomi delle colonne con il prefisso della domanda originale
        encoded_columns = [f"{col_name} {choice}" for choice in mlb.classes_]
        
        # Creiamo il DataFrame con le colonne codificate
        encoded_df_temp = pd.DataFrame(encoded_matrix, columns=encoded_columns, index=df.index)
        
        # Aggiungiamo le nuove colonne al risultato finale
        df_encoded = pd.concat([df_encoded, encoded_df_temp], axis=1)
            
    return df_encoded
In [12]:
# Applichiamo la funzione alle domande a scelta multipla
df_encoded = onehot_encode_multiselect_columns(questionario, toSplitAndHotEncode)
In [13]:
# PULIZIA DEI DATI
# Rimuoviamo le righe senza identificativo (colonna "Quali?")
# Questo assicura che abbiamo solo risposte complete
encoded_df.dropna(subset=["Quali?"], inplace=True)
df_encoded.dropna(subset=["Quali?"], inplace=True)

3. Unione dei Dataset¶

Ora uniamo tutti i dati processati in un dataset finale che conterrà:

  • Risposte del questionario (convertite in formato numerico)
  • Informazioni sui luoghi frequentati
  • Tutte le variabili pronte per l'analisi delle correlazioni
In [14]:
# Uniamo i due dataframe processati usando la colonna "Quali?" come chiave
finalDf = pd.merge(encoded_df, df_encoded, on="Quali?", how="outer")
In [15]:
# Puliamo anche il dataset degli spazi
spazi.dropna(subset=["Quali?"], inplace=True)
In [16]:
# Rimuoviamo colonne duplicate o non necessarie dal dataset spazi
spazi.drop(['Quanto ti senti accoltə negli spazi che frequenti?'], axis=1, inplace=True)
In [17]:
# Uniamo anche i dati sui luoghi frequentati
finalDf = pd.merge(finalDf, spazi, on="Quali?", how="outer")
In [18]:
# Rimuoviamo la colonna identificativa perché non serve più
finalDf.drop(["Quali?"], axis=1, inplace=True)

4. Calcolo delle Correlazioni¶

Questa è la parte centrale dell'analisi. Calcoleremo le correlazioni tra tutte le variabili per identificare:

  • Quali caratteristiche demografiche influenzano le opinioni
  • Come le abitudini di frequentazione si collegano alla soddisfazione
  • Quali fattori sono più importanti per il benessere dei giovani baresi

Come Leggere le Correlazioni¶

  • 0.4 - 0.6: correlazione moderata
  • 0.6 - 0.8: correlazione forte
  • 0.8 - 1.0: correlazione molto forte
  • Valori negativi: correlazione inversa (una aumenta, l'altra diminuisce)
In [19]:
# ELENCO DELLE DOMANDE DEL QUESTIONARIO
# Ogni elemento rappresenta una categoria di domande che analizzeremo
questions = [
    "Di che genere sei?",
    "Quanti anni hai?", 
    "Qual è la tua occupazione?",
    "Che rapporto hai con la città di Bari?",
    "Per te partecipare a incontri di aggregazione sociale, eventi culturali e dibattiti politici è:",
    "Bari offre spazi di aggregazione sociale e culturale?",
    "Ritieni che Bari sia una città a misura di persone Under 30?",
    "Quanto ti senti accoltə negli spazi che frequenti?",
    "Ritieni che questi spazi conoscano e siano attenti ai bisogni della comunità che li frequentano?",
    "Come giudichi il dialogo tra questi spazi?",
    "Partecipi a eventi culturali a Bari?",
    "Hai mai sentito parlare di Scomodo?",
    "Se sì, ti piacerebbe una Redazione di Scomodo a Bari?",
    "Come incide sul tuo benessere il rapporto che hai con la città di Bari?",
    "Sei a Bari, sei felice?",
    "A cosa è dovuta, se c'è, la tua difficoltà nella partecipazione?",
    "A che tipo di eventi partecipi?",
    "Come pensi che vadano gestiti questi fenomeni?",
    "Luogo"
]
In [20]:
# FUNZIONE PER CALCOLARE LA MATRICE DI CORRELAZIONE
def generate_correlation_matrix(df, columns_set1, columns_set2, method='pearson'): 
    """
    Calcola la correlazione tra due gruppi di variabili.
    
    Parametri:
    - df: il dataset
    - columns_set1: primo gruppo di variabili
    - columns_set2: secondo gruppo di variabili 
    - method: tipo di correlazione ('pearson' è il più comune)
    
    Restituisce una matrice con i valori di correlazione.
    """
    
    # Verifichiamo che tutte le colonne esistano nel dataset
    missing_cols1 = set(columns_set1) - set(df.columns)
    missing_cols2 = set(columns_set2) - set(df.columns)
    
    if missing_cols1:
        raise ValueError(f"Columns not found in set 1: {missing_cols1}")
    if missing_cols2:
        raise ValueError(f"Columns not found in set 2: {missing_cols2}")
    
    # Convertiamo i valori booleani in numeri (True=1, False=0)
    # per poter calcolare le correlazioni
    df_numeric = df[columns_set1 + columns_set2].astype(int)
    
    # Creiamo la matrice di correlazione vuota
    correlation_matrix = pd.DataFrame(
        index=columns_set1,
        columns=columns_set2
    )
    
    # Calcoliamo la correlazione per ogni coppia di variabili
    for col1 in columns_set1:
        for col2 in columns_set2:
            correlation_matrix.loc[col1, col2] = df_numeric[col1].corr(
                df_numeric[col2], method=method
            )
    
    # Convertiamo in formato numerico
    correlation_matrix = correlation_matrix.astype(float)
    
    return correlation_matrix
In [21]:
# ORGANIZZAZIONE DELLE VARIABILI PER DOMANDA
# Raggruppiamo tutte le colonne che appartengono a ciascuna domanda
# Esempio: "Di che genere sei?" potrebbe avere colonne come
# "Di che genere sei? Maschio", "Di che genere sei? Femmina", ecc.

prefix_to_columns = {}

for question in questions:
    # Trova tutte le colonne che iniziano con questa domanda
    matching_columns = [col.replace(question + " ", "") for col in finalDf.columns if col.startswith(question)]
    prefix_to_columns[question] = matching_columns
In [22]:
# FUNZIONE AUSILIARIA PER PULIRE I NOMI DEI FILE
def sanitize_filename(filename):
    """Rimuove caratteri non validi dai nomi dei file"""
    invalid_chars = '<>:"|?*/'
    for char in invalid_chars:
        filename = filename.replace(char, '')
    return filename.strip()
In [23]:
# FUNZIONE PER CREARE GRAFICI DELLE CORRELAZIONI
# (Questa funzione è definita ma non utilizzata nel flusso principale)
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

def plot_correlation_heatmap(correlation_matrix, title="Correlation Matrix", figsize=(12, 8), 
                           cmap='RdBu_r', center=0, annot=True, fmt='.2f', 
                           cbar_kws=None, save_path=None):
    """
    Crea un grafico a mappa di calore per visualizzare le correlazioni.
    
    I colori rappresentano l'intensità della correlazione:
    - Rosso: correlazione negativa
    - Bianco: nessuna correlazione
    - Blu: correlazione positiva
    """
    # Configura il grafico
    fig, ax = plt.subplots(figsize=figsize)
    
    # Crea la mappa di calore
    sns.heatmap(
        correlation_matrix,
        annot=annot,        # Mostra i valori numerici
        cmap=cmap,          # Schema di colori
        center=center,      # Centro della scala di colori
        fmt=fmt,            # Formato dei numeri
        square=False,
        cbar_kws=cbar_kws or {},
        ax=ax
    )
    
    # Personalizza il grafico
    ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
    
    # Ruota le etichette per una migliore leggibilità
    ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')
    ax.set_yticklabels(ax.get_yticklabels(), rotation=0)
    
    # Adatta il layout per evitare che le etichette vengano tagliate
    plt.tight_layout()
    
    # Salva se richiesto
    if save_path:
        plt.savefig(save_path, dpi=300, bbox_inches='tight')
    
    # Mostra il grafico
    plt.show()
    
    return fig, ax

5. Estrazione delle Correlazioni Significative¶

Ora calcoliamo tutte le possibili correlazioni e filtriamo solo quelle significative (≥ 0.4).

Il processo:

  1. Calcola correlazioni tra ogni coppia di domande
  2. Mantiene solo quelle con correlazione ≥ 0.4
  3. Seleziona i risultati più interessanti per ogni domanda
  4. Organizza i dati per la visualizzazione finale
In [24]:
# CODICE COMMENTATO - VERSIONE CHE SALVA FILE SEPARATI
# Questo codice salverebbe ogni matrice di correlazione in un file separato
# e creerebbe grafici per ogni coppia significativa di domande

# os.makedirs("matrices/", exist_ok=True)
# for question1, values1 in prefix_to_columns.items():
#     for question2, values2 in prefix_to_columns.items():
#         if question1 != question2:
#             features = [question1 + " " + val for val in values1]
#             targets = [question2 + " " + val for val in values2]
#             try:
#                 corr_matrix = generate_correlation_matrix(finalDf, features, targets)
                
#                 if corr_matrix.max().max() >= 0.4:
#                     corr_matrix.to_json("matrices/" + sanitize_filename(question1) + "-" + sanitize_filename(question2) + ".json", index=False, indent=4, force_ascii=False)
#                     plot_correlation_heatmap(
#                         corr_matrix, 
#                         title="Correlation Matrix: Features vs Targets",
#                         figsize=(8, 6)
#                     )
#             except Exception as e:
#                 print ("Failed to process matrix for", question1, "and", question2)
In [25]:
# CALCOLO DI TUTTE LE CORRELAZIONI SIGNIFICATIVE
# Questo ciclo calcola le correlazioni tra ogni coppia di domande
# e mantiene solo quelle con correlazione massima ≥ 0.4

out = {}  # Dizionario per memorizzare i risultati

for question1, values1 in prefix_to_columns.items():
    for question2, values2 in prefix_to_columns.items():
        if question1 != question2:  # Non correliamo una domanda con se stessa
            # Ricostruiamo i nomi completi delle colonne
            features = [question1 + " " + val for val in values1]
            targets = [question2 + " " + val for val in values2]
            
            try:
                # Calcoliamo la matrice di correlazione
                corr_matrix = generate_correlation_matrix(finalDf, features, targets)
                
                # Manteniamo solo se c'è almeno una correlazione significativa
                if corr_matrix.max().max() >= 0.4:
                    # Salviamo in formato JSON per elaborazione successiva
                    key = sanitize_filename(question1) + "-" + sanitize_filename(question2)
                    out[key] = corr_matrix.to_json(index=False, indent=4, force_ascii=False)
                    
            except Exception as e:
                # Stampiamo errori ma continuiamo l'elaborazione
                print ("Failed to process matrix for", question1, "and", question2)

6. Organizzazione Finale dei Risultati¶

Ultimo passo: organizziamo i risultati in un formato facilmente utilizzabile per creare visualizzazioni e report.

Il processo:

  1. Elimina duplicati: evita di avere sia "A vs B" che "B vs A"
  2. Seleziona i top risultati: per ogni domanda, mantiene solo le correlazioni più forti
  3. Pulisce i nomi: rimuove testo ridondante per una migliore leggibilità
  4. Salva in formato JSON: per utilizzo in dashboard o altre applicazioni
In [26]:
# RIMOZIONE DEI DUPLICATI
# Alcune coppie di domande appaiono due volte (A-B e B-A)
# Manteniamo solo una versione per evitare ridondanza

processed_pairs = set()  # Tiene traccia delle coppie già processate
filtered_out = {}        # Risultati filtrati

for title in out.keys():
    split = title.split("-")
    # Normalizziamo l'ordine delle domande per identificare duplicati
    pair = tuple(sorted([split[0].strip(), split[1].strip()]))
    
    if pair not in processed_pairs:
        processed_pairs.add(pair)
        filtered_out[title] = out[title]

# ORGANIZZAZIONE FINALE DEI DATI
# Processamento dei risultati filtrati per creare la struttura finale
full = {}

for title, x in filtered_out.items():
    value = json.loads(x)  # Convertiamo da JSON a dizionario Python
    split = title.split("-")
    
    # Struttura base per ogni coppia di domande
    full[title] = {
        "axis1": split[1].strip(),  # Prima domanda
        "axis2": split[0].strip(),  # Seconda domanda  
        "data": {}                  # Dati delle correlazioni
    }

    # SELEZIONE DEI TOP RISULTATI
    # Per ogni domanda, selezioniamo le 10 correlazioni più forti
    top_k1_keys = sorted(
        value.items(),
        key=lambda kv: max(abs(v) for v in kv[1].values()),  # Ordina per correlazione massima
        reverse=True
    )[:10]  # Prendi i primi 10

    # Riordina alfabeticamente per consistenza
    top_k1_keys = sorted(top_k1_keys, key=lambda kv: kv[0])

    for k1, k2v in top_k1_keys:
        # Pulizia del nome della prima variabile
        newK1 = k1.replace(split[1], "").replace("?", "").replace(":  ", "").strip()

        # Selezione delle top correlazioni per la seconda variabile
        top_k2_items = sorted(
            k2v.items(),
            key=lambda kv: abs(kv[1]),  # Ordina per valore assoluto della correlazione
            reverse=True
        )[:10]  # Prendi i primi 10

        # Riordina alfabeticamente
        top_k2_items = sorted(top_k2_items, key=lambda kv: kv[0])

        for k2, v in top_k2_items:
            # Pulizia del nome della seconda variabile
            newK2 = k2.replace(split[0], "").replace("?", "").strip()
            
            # Aggiunta al risultato finale
            if newK1 not in full[title]["data"]:
                full[title]["data"][newK1] = {}
            full[title]["data"][newK1][newK2] = v

# Ordina il dizionario finale alfabeticamente
full = dict(sorted(full.items()))

7. Salvataggio dei Risultati¶

Salviamo i risultati dell'analisi in un file JSON chiamato toPlot.json.

Struttura del File di Output¶

Il file contiene:

  • Chiavi: nomi delle coppie di domande correlate
  • axis1/axis2: le due domande messe in relazione
  • data: matrice con i valori di correlazione specifici

Come Utilizzare i Risultati¶

Questo file può essere utilizzato per:

  • Creare dashboard interattive
  • Generare report automatici
  • Identificare pattern interessanti nei dati
  • Guidare ulteriori ricerche qualitative
In [27]:
# SALVATAGGIO DEL FILE FINALE
# Salviamo tutti i risultati in un file JSON per utilizzo successivo
with open ("toPlot.json", "w", encoding='utf-8') as f:
    json.dump(full, f, indent=4, ensure_ascii=False)